这一步中,您将实现拖放功能。
在本节中,您将首先使用 Kanzi Engine API 实例化您在本教程上一步创建的 Drag Item 预设件模板。然后定义用户开始拖放手势和拖动按钮时的行为。最后,您将创建并配置导航栏中各按钮的拖放操纵器。
要创建拖动功能:
onProjectLoaded() 函数中实例化用于可视化用户正在拖动的按钮的Drag Item 预设件: virtual void onProjectLoaded() KZ_OVERRIDE
{
...
//获取 Drag Item 预设件的引用。
ResourceManager* resourceManager = getDomain()->getResourceManager();
PrefabTemplateSharedPtr dragItemPrefab = resourceManager->acquireResource<PrefabTemplate>("kzb://drag_and_drop/Prefabs/Drag Item");
//实例化 Drag Item 预设件。
m_dragItem = dragItemPrefab->instantiate<Node2D>("Drag Item");
//获取使用其别名的 RootPage 节点。
Node2DSharedPtr rootPage = screen->lookupNode<Node2D>("#RootPage");
//为RootPage 节点添加您创建的 Drag Item 预设件实例。
rootPage->addChild(m_dragItem);
//禁用 Drag Item 预设件实例的可见性 (Visible) 属性。
//当用户不拖动 Drag Item 时,将其隐藏。
m_dragItem->setVisible(false);
}
private:
...
//为实例化的 Drag Item 定义成员变量。
Node2DSharedPtr m_dragItem;
DragAndDrop 类的私有部分创建移动 Drag Item 预设件实例的函数:private:
...
//更新 Drag Item 的位置。
void updateDragAndDrop(Vector2 dragPosition, Matrix3x3 dragWorldTransform)
{
//计算局部拖动锚点,即用户正在拖动的按钮左上角。
Vector2 localDragAnchor = dragPosition - m_dragGrabOffset;
//将 Drag Item 的移动限制到 x 轴。
localDragAnchor.setY(0.0f);
//计算全局拖动锚点。
Vector2 globalDragAnchor = dragWorldTransform * localDragAnchor;
//用于描述 Drag Item 的 渲染变换 (Render Transformation) 属性的结构。
SRTValue2D transform;
//设置渲染变换 (Render Transformation) 属性平移 (Translation) 属性字段为全局拖动锚点。
transform.setTranslation(globalDragAnchor);
//移动 Drag Item 拖动的距离量。
m_dragItem->setRenderTransformation(transform);
//设置按钮图标。
updateItems();
}
...
//定义用户按下或点击按钮时该按钮左上角偏移的成员变量。
Vector2 m_dragGrabOffset;
};
DragAndDrop 类的私有部分定义 DragAndDropManipulator::StartedMessage 消息的处理程序:private:
...
// 定义 2D 节点的 DragAndDropManipulator::StartedMessage 消息处理程序
//这些节点带有生成拖放消息的输入操纵器。
//这样可为拖动准备 2D 节点。
void onDragStarted(DragAndDropManipulator::StartedMessageArguments& messageArguments)
{
//从消息参数获得用户开始拖动的按钮。
Node2DSharedPtr dragSourceItem = dynamic_pointer_cast<Node2D>(messageArguments.getSource());
//获取用户开始拖动的按钮大小。
Vector2 dragSourceItemSize = dragSourceItem->getActualSize();
//将 Drag Item 设置为和该按钮相同的大小。
m_dragItem->setSize(dragSourceItemSize.getX(), dragSourceItemSize.getY());
//移动 Drag Item 到正确的位置。
updateDragAndDrop(messageArguments.getPoint(), dragSourceItem->getWorldTransform());
//让 Drag Item 可见。
m_dragItem->setVisible(true);
}
...
onDragStarted 函数后面定义 DragAndDropManipulator::MovedMessage 消息的处理程序: // 定义 2D 节点的 DragAndDropManipulator::MovedMessage 消息处理程序
//这些节点带有生成拖放消息的输入操纵器。
void onDragMoved(DragAndDropManipulator::MovedMessageArguments& messageArguments)
{
//从消息参数获得用户正在拖动的按钮。
Node2DSharedPtr dragSourceItem = dynamic_pointer_cast<Node2D>(messageArguments.getSource());
//移动 Drag Item 并更新按钮图标。
updateDragAndDrop(messageArguments.getPoint(), dragSourceItem->getWorldTransform());
}DragAndDrop 类的私有部分添加创建和配置节点拖放操纵器的函数: // 创建并配置节点的拖放操纵器。
void createDragAndDropManipulator(NodeSharedPtr dragSourceItem)
{
Domain* domain = getDomain();
//创建生成拖放消息的输入操纵器。
DragAndDropManipulatorSharedPtr dragAndDropManipulator = DragAndDropManipulator::create(domain);
//添加输入操纵器到该节点。
dragSourceItem->addInputManipulator(dragAndDropManipulator);
//在拖放开始前设置长按持续时间为 200 ms。默认值为 500 ms。
//这是用户在开始拖动节点前必须按下节点的时间。
dragAndDropManipulator->setPressDuration(chrono::milliseconds(200));
//订阅该节点的 DragAndDropManipulator::StartedMessage 消息。
// DragAndDropManipulator 在用户按下该节点
//获取 DragAndDropManipulator::setPressDuration 设置的持续时间时生成此消息。
dragSourceItem->addMessageHandler(DragAndDropManipulator::StartedMessage, bind(&DragAndDrop::onDragStarted, this, placeholders::_1));
//订阅该节点的 DragAndDropManipulator::MovedMessage 消息。
// DragAndDropManipulator 在指针移动时生成此消息。
dragSourceItem->addMessageHandler(DragAndDropManipulator::MovedMessage, bind(&DragAndDrop::onDragMoved, this, placeholders::_1));
}onProjectLoaded() 函数末尾为各按钮调用 createDragAndDropManipulator 函数: virtual void onProjectLoaded() KZ_OVERRIDE
{
...
//创建各按钮的拖放操纵器。
//使用别名获取按钮节点。
createDragAndDropManipulator(screen->lookupNode<Node>("#Navigation"));
createDragAndDropManipulator(screen->lookupNode<Node>("#Phone"));
createDragAndDropManipulator(screen->lookupNode<Node>("#Applications"));
createDragAndDropManipulator(screen->lookupNode<Node>("#Music"));
createDragAndDropManipulator(screen->lookupNode<Node>("#Car"));
}这一节中,您将设置用户拖动的按钮图标和位置,并在用户拖动其中一个按钮时重定位按钮图标。
要完成拖动功能:
onDragStarted 函数中,在调用 updateDragAndDrop 函数之前,设置 Drag Item 的图标并正确定位该节点:void onDragStarted(DragAndDropManipulator::StartedMessageArguments& messageArguments)
{
...
//获取用户开始拖动的按钮的数据上下文对象。
m_draggedDataContext = dynamic_pointer_cast<DataObject>(dragSourceItem->getProperty(DataContext::DataContextProperty));
//将 Drag Item 的数据上下文 (Data Context) 属性设置为该按钮的数据上下文。
//这样即可将 Drag Item 设置为具有和用户开始拖动的按钮相同的图标。
m_dragItem->setProperty(DataContext::DataContextProperty, m_draggedDataContext);
//保存用户相对于节点原点(默认情况下为左上角)
//开始拖动节点的起点。
m_dragGrabOffset = messageArguments.getPoint();
...
}
...
//为用户拖动的按钮的数据上下文对象定义成员变量。
DataObjectSharedPtr m_draggedDataContext;
};
updateDragAndDrop 函数中,在调用 updateItems() 函数之前,添加代码,在用户拖动其中一个按钮时重定位所有按钮的图标: void updateDragAndDrop(Vector2 dragPosition, Matrix3x3 dragWorldTransform)
{
...
//获取全局指针位置。
Vector2 globalPointerPosition = dragWorldTransform * dragPosition;
//将全局坐标转换为 2D 网格布局 (Grid layout 2D) 节点的本地坐标。
Vector2 hitTestPoint = *m_grid->globalToLocal(globalPointerPosition);
//获取按钮的宽度。
float cellWidth = m_grid->getActualColumnSize(0);
//计算2D 网格布局 (Grid layout 2D) 节点中按钮的索引。
unsigned int cellIndex = 0;
if (hitTestPoint.getX() > 0.0f)
{
cellIndex = static_cast<unsigned int>(hitTestPoint.getX() / cellWidth);
cellIndex = min(cellIndex, m_grid->getChildCount() - 1u);
}
//从旧位置移除数据对象。
m_rootData->removeChild(*m_draggedDataContext);
//插入数据对象到新位置。
m_rootData->insertChild(cellIndex, m_draggedDataContext);
...
}
updateItems() 函数中,在 for 循环中添加 if-else 从句,在用户拖动时隐藏按钮图标: void updateItems()
{
...
for (; dataIt != endDataIt; dataIt++, nodeIt++)
{
...
//如果按钮节点是用户正在拖动的节点,将其隐藏。
//隐藏该节点,因为使用 Drag Item 可视化该节点的拖动。
if (m_draggedDataContext && itemData == m_draggedDataContext)
{
itemNode->setVisible(false);
}
else
{
itemNode->setVisible(true);
}
}
}在之前的部分,实现了按钮的拖放。当用户结束拖放手势时,Drag Item 在用户释放指针的位置保持可见。在本节中,您将添加代码,让按钮看上去像落入用户放置它的位置。
要创建放置功能:
onDragMoved 函数后面定义 DragAndDropManipulator::FinishedMessage 消息的处理程序: // 定义 2D 节点的 DragAndDropManipulator::FinishedMessage 消息处理程序
//这些节点带有生成拖放消息的输入操纵器。
void onDragFinished(DragAndDropManipulator::FinishedMessageArguments&)
{
//隐藏 Drag Item 预设件实例。
m_dragItem->setVisible(false);
//清除至数据上下文对象的指针。
m_draggedDataContext.reset();
//分配正确的图标到按钮。
updateItems();
}createDragAndDropManipulator 函数的末尾订阅该节点的 DragAndDropManipulator::FinishedMessage 消息: // 创建并配置节点的拖放操纵器。
void createDragAndDropManipulator(NodeSharedPtr dragAndDropNode)
{
...
//订阅该节点的 DragAndDropManipulator::FinishedMessage 消息。
//当用户松开指针结束拖放手势时,DragAndDropManipulator
//生成此消息。
dragSourceItem->addMessageHandler(DragAndDropManipulator::FinishedMessage, bind(&DragAndDrop::onDragFinished, this, placeholders::_1));
}构建和运行应用程序。
结束拖放手势时,Drag Item 变得不可见。
要详细了解拖放输入操纵器,请参阅使用拖放操纵器。
要详细了解关于在 Kanzi 中处理用户输入的详细信息,请参阅处理用户输入。